Java Final与Effectively Final
(给ImportNew加星标,提高Java技能)
编译:ImportNew/覃佑桦
baeldung.com/java-effectively-final
1. 介绍
Java 8 引入的众多功能中,其中一个最有趣的功能是 effectively final。即不用 final 修饰符也能达到同样的效果。
本文将介绍该功能的起源以及编译器处理 effectively final 与 final 关键字的不同之处。此外,还会通过一个 effectively final 变量的问题案例给出解决方案。
2. Effectively Final 的起源
简而言之,如果对象或基础类型的变量在初始化后值不发生改变,则可以把它们看做 effectively final。只要不改变对象引用,即使引用的对象发生状态改变,该对象也是 effectively final。
在 Java 引入该功能之前,不能在匿名类中使用非 final 局部变量。此外,也不能在匿名类、内部类和 lambda 表达式中多次赋值。新功能的加入节省了为 effectively final 变量输入 final 关键字的工作。
匿名类是一种内部类,不能访问非 final 变量或 effectively final 变量,也无法按照 JLS 8.1.3 的规定在其封闭作用域内的变量进行修改。lambda 表达式也有类似的限制,修改变量可能会带来并发问题。
3. Final vs Effectively Final
要确认一个 final 变量是不是 effectively final,最简单的办法就是删除 final 关键字看能否编译并运行:
@FunctionalInterface
public interface FunctionalInterface {
void testEffectivelyFinal();
default void test() {
int effectivelyFinalInt = 10;
FunctionalInterface functionalInterface
= () -> System.out.println("Value of effectively variable is : " + effectivelyFinalInt);
}
}
重新赋值或者改变 effectively final 都会报告无效代码。
3.1 编译器处理
JLS 4.12.4 指出,从方法参数或局部变量中删除 final 修饰符且不产生编译错误,则该变量为 effectively final。在程序中为变量声明加上 final 关键字,那么该变量也会变成 effectively final。
docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12
与 final 变量不同,Java 编译器不会对 effectively final 变量进行额外优化。
下面这个简单的示例中声明两个 final String 变量,仅用作字符串连接:
public static void main(String[] args) {
final String hello = "hello";
final String world = "world";
String test = hello + " " + world;
System.out.println(test);
}
编译器会将上面 main 方法实际执行的代码变成下面这样:
public static void main(String[] var0) {
String var1 = "hello world";
System.out.println(var1);
}
去掉 final 关键字,这些变量将被视为 effectively final。但编译器不会因为它们仅用作字符串连接而对它们优化。
4. 原子操作
在 lambda 表达式和匿名类中修改变量不是一个好习惯。因为我们不知道这些变量在方法块中会如何使用,在多线程环境中修改也可能会得到意外的结果。
关于使用 lambda 表达式的最佳实践已经有了一个教程,另外还有一个教程关于修改lambda 表达式中常见的反模式。有一种替代方案可以在这种场景中修改变量,通过原子性实现线程安全。
最佳实践: baeldung.com/java-8-lambda-expressions-tips
反模式教程: baeldung.com/java-lambda-effectively-final-local-variables
java.util.concurrent.atomic 提供了像 AtomicReference 和 AtomicInteger 这样的类。可以使用原子操作修改 lambda 表达式中的变量:
public static void main(String[] args) {
AtomicInteger effectivelyFinalInt = new AtomicInteger(10);
FunctionalInterface functionalInterface = effectivelyFinalInt::incrementAndGet;
}
5. 总结
本文介绍了 final 变量和 effectively final 变量的区别,并且介绍了一种安全修改 lambda 函数变量的方案。
看完本文有收获?请转发分享给更多人
关注「ImportNew」,提升Java技能
好文章,我在看❤️